package edu.washington.ling.syntric.proc;

import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import edu.stanford.nlp.util.Pair;
import edu.washington.ling.syntric.config.Configuration;
import edu.washington.ling.syntric.io.*;
import edu.washington.ling.syntric.struct.error.*;
import edu.washington.ling.syntric.struct.projection.*;
import edu.washington.ling.syntric.struct.rule.*;
import edu.washington.ling.syntric.struct.tree.Node;
import edu.washington.ling.syntric.struct.tree.Token;
import edu.washington.ling.syntric.struct.tree.Tree;
import edu.washington.ling.syntric.util.ConvertUtils;
import edu.washington.ling.syntric.util.TreeUtils;

public class ApplyRules {
		
	private static ApplyRulesOptions opts = new ApplyRulesOptions();
	
	private static PrintingExceptionHandler pHandler = new PrintingExceptionHandler();
		
	public static LinkedList<BiProjectionAssembly> getLeftBPAQueue(Token t, Tree ds, 
			Map<BiDSPattern, List<BiPSPattern>> biRuleMap, Configuration config) {
		LinkedList<BiProjectionAssembly> bpaQueue = new LinkedList<BiProjectionAssembly>();
		List<Token> leftChildren = TreeUtils.getLeftDSChildren(t, ds);
		for (Token child: leftChildren) {
			BiProjectionAssembly bpa = new BiProjectionAssembly();
			BiDSPattern dsPatt = ConvertUtils.extractBinaryDSPattern(child, t, config.getDepFeats(), 
																	 config.getHeadFeats(), config.useExtPOS());
			if (config.extractOrder()) {
				dsPatt.setHeadFinal(true);
			}
			if (biRuleMap.containsKey(dsPatt)) {
				List<BiPSPattern> psPatts = biRuleMap.get(dsPatt);
				bpa.addProjectionsFromPSPatts(psPatts, child, true);
				if (bpa.isEmpty()) {
					System.out.println("(*) PS patterns could not be applied to existing structure.");
					System.out.println(String.format("DS child has children = %b", child.hasDSDependents()));
					for (Token childChild: child.getDSDependents()) {
						System.out.print(String.format("%s ", childChild.getTag()));
					}
					System.out.println(String.format("Parent: word=%s tag=%s, Child: word=%s tag=%s", t.getWord(), t.getTag(), child.getWord(), child.getTag()));
					System.out.print("Roots of child projections: ");
					for (ProjectionNode projChain: child.getProjForest()) {
						System.out.print(String.format("\"%s\" ", projChain.getRootLabel()));
					}
					System.out.println();
					System.out.print("Children of ps patterns: ");
					for (BiPSPattern psPatt: psPatts) {
						System.out.print(String.format("\"%s\" ", psPatt.getDepChild().getLabel()));
					}
					System.out.println();
				} 
				
			} else {
				System.out.println("(**) Binary DS pattern was not found.");
			}
			bpaQueue.addFirst(bpa);
		}
		return bpaQueue;
	}
	
	public static LinkedList<BiProjectionAssembly> getRightBPAQueue(Token t, Tree ds, 
			Map<BiDSPattern, List<BiPSPattern>> biRuleMap, Configuration config) {
		LinkedList<BiProjectionAssembly> bpaQueue = new LinkedList<BiProjectionAssembly>();
		List<Token> rightChildren = TreeUtils.getRightDSChildren(t, ds);
		for (Token child: rightChildren) {
			BiProjectionAssembly bpa = new BiProjectionAssembly();
			BiDSPattern dsPatt = ConvertUtils.extractBinaryDSPattern(child, t, config.getDepFeats(), 
					 												 config.getHeadFeats(), config.useExtPOS());
			if (config.extractOrder()) {
				dsPatt.setHeadFinal(false);
			}
			if (biRuleMap.containsKey(dsPatt)) {
				List<BiPSPattern> psPatts = biRuleMap.get(dsPatt);
				bpa.addProjectionsFromPSPatts(psPatts, child, false);
				if (bpa.isEmpty()) {
					System.out.println("(*) PS patterns could not be applied to existing structure.");
					System.out.println(String.format("DS child has children = %b", child.hasDSDependents()));
					for (Token childChild: child.getDSDependents()) {
						System.out.print(String.format("%s ", childChild.getTag()));
					}
					System.out.println(String.format("Parent: word=%s tag=%s, Child: word=%s tag=%s", t.getWord(), t.getTag(), child.getWord(), child.getTag()));
					System.out.print("Roots of child projections: ");
					for (ProjectionNode projChain: child.getProjForest()) {
						System.out.print(String.format("\"%s\" ", projChain.getRootLabel()));
					}
					System.out.println();
					System.out.print("Children of ps patterns: ");
					for (BiPSPattern psPatt: psPatts) {
						System.out.print(String.format("\"%s\" ", psPatt.getDepChild().getLabel()));
					}
					System.out.println();
				}
				
			} else {
				System.out.println("(**) Binary DS pattern was not found.");
			}
			bpaQueue.addFirst(bpa);
		}
		return bpaQueue;
	}
	
	public static List<ProjectionNode> buildBinaryProjectionChains(ProjectionNode projChain, 
												  LinkedList<BiProjectionAssembly> leftBPAQueue, 
												  LinkedList<BiProjectionAssembly> rightBPAQueue) {
		
		List<ProjectionNode> newProjChains = new LinkedList<ProjectionNode>();

		if (leftBPAQueue.size() > 0) {
			BiProjectionAssembly leftBPA = leftBPAQueue.peekFirst();
			// Try to join head-children to roots.
			if (leftBPA.hasHeadChild(projChain.getRootLabel())) {
				LinkedList<BiProjectionAssembly> newLeftBPAQueue = new LinkedList<BiProjectionAssembly>(leftBPAQueue);
				newLeftBPAQueue.removeFirst();
				List<ProjectionNode> leftChains = leftBPA.connectNewChainsToRootOf(projChain);
				//System.out.println(String.format("Getting LEFT chains for projChain=\'%s\'", printChain(projChain)));
				//for (ProjectionNode leftChain: leftChains) {
				//	System.out.println(printChain(leftChain));
				//}
				for (ProjectionNode leftChain: leftChains) {
					newProjChains.addAll(buildBinaryProjectionChains(leftChain, newLeftBPAQueue, rightBPAQueue));
				}
			}
		}
		if (rightBPAQueue.size() > 0) {
			BiProjectionAssembly rightBPA = rightBPAQueue.peekLast();
			// Try to join head-children to roots.
			if (rightBPA.hasHeadChild(projChain.getRootLabel())) {
				LinkedList<BiProjectionAssembly> newRightBPAQueue = new LinkedList<BiProjectionAssembly>(rightBPAQueue);
				newRightBPAQueue.removeLast();
				List<ProjectionNode> rightChains = rightBPA.connectNewChainsToRootOf(projChain);
				//System.out.println(String.format("Getting RIGHT chains for projChain=\'%s\'", printChain(projChain)));
				//for (ProjectionNode rightChain: rightChains) {
				//	System.out.println(printChain(rightChain));
				//}
				for (ProjectionNode rightChain: rightChains) {
					newProjChains.addAll(buildBinaryProjectionChains(rightChain, leftBPAQueue, newRightBPAQueue));
				}
			}
			
		}
		if (leftBPAQueue.size() == 0 && rightBPAQueue.size() == 0) {
			newProjChains.add(projChain);
		}
		return newProjChains;
	}

	/*
	public static List<ProjectionNode> buildBinaryProjectionChainsFromTop(ProjectionNode uniProjChain, ProjectionNode biProjChain,
			  LinkedList<BiProjectionAssembly> leftBPAQueue, 
			  LinkedList<BiProjectionAssembly> rightBPAQueue) {
	*/

	
	public static List<ProjectionNode> buildUnaryProjectionChains(Token t, Tree ds, 
			Map<UniDSPattern, List<UniPSPattern>> uniRuleMap, Configuration config) {
		UniDSPattern dsPatt = ConvertUtils.extractUnaryDSPattern(t, config.getHeadFeats(), config.useExtPOS());
		UniProjectionAssembly upa = new UniProjectionAssembly();
		if (uniRuleMap.containsKey(dsPatt)) {
			List<UniPSPattern> psPatts = uniRuleMap.get(dsPatt);
			upa.addProjectionsFromPSPatts(psPatts, t.getWord(), ds.getTokenPosition(t));
		}
		else {
			System.out.println(String.format("(**) Unary DS pattern %s not found.", dsPatt.getDSToken().getTag()));
		}
		return upa.getProjectionChains();
	}
	
	public static String printChain(ProjectionNode projChain) {
		String projChainStr = String.format("%s ", projChain.getRootLabel());
		if (projChain.hasChild()) {
			projChainStr = projChainStr.concat(printChain(projChain.getChild()));
		}
		return projChainStr;
	}
	
	public static void buildProjectionChains(Token t, Tree ds, Map<UniDSPattern, List<UniPSPattern>> uniRuleMap, 
			Map<BiDSPattern, List<BiPSPattern>> biRuleMap, Configuration config) {
		if (t.hasDSDependents()) {
			for (Token child: t.getDSDependents()) {
				buildProjectionChains(child, ds, uniRuleMap, biRuleMap, config);
			}
			List<ProjectionNode> uniChains = buildUnaryProjectionChains(t, ds, uniRuleMap, config);
			LinkedList<BiProjectionAssembly> leftBPAQueue = getLeftBPAQueue(t, ds, biRuleMap, config);
			LinkedList<BiProjectionAssembly> rightBPAQueue = getRightBPAQueue(t, ds, biRuleMap, config);
			List<ProjectionNode> biChains = new LinkedList<ProjectionNode>();
			for (ProjectionNode uniChain: uniChains) {
				biChains.addAll(buildBinaryProjectionChains(uniChain, leftBPAQueue, rightBPAQueue));
			}
			System.out.println(String.format("Token \'%s\' has %d chains", t.getWord(), biChains.size()));
			t.addChainsToProjForest(biChains);
		}
		else {
			List<ProjectionNode> uniChains = buildUnaryProjectionChains(t, ds, uniRuleMap, config);
			t.addChainsToProjForest(uniChains);
		}
	}
	
	/***
	 * FILTER STACKING ORDERS (in future filter w/flag, not by deleting)
	 */
	public static void filterStackingOrders(Tree tree, Map<String, String> rules) {
		for (Token t: tree.getTokenList()) {
			filterStackingOrdersForToken(t, rules);
		}
	}
	public static void filterStackingOrdersForToken(Token t, Map<String, String> rules) {
		List<ProjectionNode> projChains = t.getProjForest();
		List<ProjectionNode> newProjChains = new LinkedList<ProjectionNode>();
		String tag = t.getTag();
		for (ProjectionNode projChain: projChains) {
			String rootLabel = projChain.getRootLabel();
			String lhs = tag + "_" + rootLabel;
			if (rules.containsKey(lhs)) {
				String constraint = rules.get(lhs);
				if (isStackingOrderValid(projChain, rootLabel, constraint)) {
					newProjChains.add(projChain);
				}
			}
			else {
				newProjChains.add(projChain);
			}
		}
		t.setProjForest(newProjChains);
	}
	public static boolean isStackingOrderValid(ProjectionNode projChain, 
			String rootLabel, String constraint) {
		boolean isValid = true;
		if (constraint.equals("rightTop"))
			isValid = isStackingOrderRightTop(projChain, rootLabel);
		return isValid;
	}
	public static boolean isStackingOrderRightTop(ProjectionNode projChain, String rootLabel) {
		if (projChain.hasChild()) {
			ProjectionNode projChainChild = projChain.getChild();
			if (projChain instanceof BiProjectionNode && 
					projChainChild instanceof BiProjectionNode) {
				BiProjectionNode biProjChain = (BiProjectionNode) projChain;
				BiProjectionNode biProjChainChild = (BiProjectionNode) projChainChild;
				if (biProjChain.getRootLabel().equals(rootLabel) 
						&& biProjChainChild.getRootLabel().equals(rootLabel)
						&& biProjChain.hasLeftDependentForests() 
						&& biProjChainChild.hasRightDependentForests()) {
					return false;
				}
				else {
					return isStackingOrderRightTop(projChainChild, rootLabel);
				}
			}
			else {
				return true;
			}
		}
		else {
			return true;
		}
	}
	
	/***
	 * END FILTER 
	 */
	
	/***
	 * GET STACKING AMBIGUITY
	 */
	public static void printStackingAmbiguities(Tree ds) {
		Set<String> stackingOrders;
		int numStackingOrders;
		for (Token t: ds.getTokenList()) {
			stackingOrders = getStackingOrders(t.getProjForest());
			numStackingOrders = stackingOrders.size();
			if (numStackingOrders > 1) {
				System.out.println(String.format("Stacking: Token '%s' has %d stacking orders", t.getWord(), numStackingOrders));
				System.out.println(stackingOrders);
			}
		}
	}
	public static int getDependentPosition(ProjectionNode projNode) {
		if (projNode instanceof UniProjectionNode) {
			return ((UniProjectionNode) projNode).getIndex();
		}
		else if (projNode.hasChild()) {
			return getDependentPosition(projNode.getChild());
		}
		else {
			return -1;
		}
	}
	public static Set<String> getStackingOrders(List<ProjectionNode> projChains) {
		Set<String> stackOrders = new TreeSet<String>();
		for (ProjectionNode projChain: projChains) {
			stackOrders.addAll(getStackingOrders(projChain));
		}
		return stackOrders;
	}
	public static Set<String> getStackingOrders(ProjectionNode projChain) {
		Set<String> stackOrders = new TreeSet<String>();
		Set<String> childStackOrders = new TreeSet<String>();
		if (projChain.hasChild()) {
			childStackOrders = getStackingOrders(projChain.getChild());
		}
		if (projChain instanceof BiProjectionNode) {
			BiProjectionNode biProjChain = (BiProjectionNode) projChain;
			if (biProjChain.hasLeftDependentForests()) {
				int leftPosition = getDependentPosition(biProjChain.getInnerLeftDependentForest().get(0));
				String stackOrder;
				if (childStackOrders.size() > 0) {
					for (String childStackOrder: childStackOrders) {
						stackOrder = childStackOrder + "." + Integer.toString(leftPosition);
						stackOrders.add(stackOrder);
					}
				}
				else { 
					stackOrder = Integer.toString(leftPosition);
					stackOrders.add(stackOrder);
				}
			}
			if (biProjChain.hasRightDependentForests()) {
				int rightPosition = getDependentPosition(biProjChain.getInnerRightDependentForest().get(0));
				String stackOrder;
				if (childStackOrders.size() > 0) {
					for (String childStackOrder: childStackOrders) {
						stackOrder = childStackOrder + "." + Integer.toString(rightPosition);
						stackOrders.add(stackOrder);
					}
				}
				else {
					stackOrder = Integer.toString(rightPosition);
					stackOrders.add(stackOrder);
				}
			}
		}
		return stackOrders;
	}

	/***
	 * GET TREES
	 */
	public static Pair<Node, Node> getRootChain(List<String> rootLabels) {
		Node chainTop = null, chainMiddle = null, chainBottom = null;
		for (String rootLabel: rootLabels) {
			chainBottom = new Node();
			chainBottom.setLabel(rootLabel);
			if (chainMiddle != null) {
				chainMiddle.addPSChild(chainBottom);
			} else {
				chainTop = chainBottom;
			}
			chainMiddle = chainBottom;
		}
		return new Pair<Node, Node>(chainTop, chainBottom);
	}
	public static List<Node> getPSSubtrees(List<Node> headRoots, List<Node> depRoots, List<String> rootLabels, 
			boolean headFinal) {
		List<Node> psRoots = new LinkedList<Node>();
		for (Node headRoot: headRoots) {
			for (Node depRoot: depRoots) {
				Pair<Node, Node> rootChain = getRootChain(rootLabels);
				Node chainTop = rootChain.first();
				Node chainBottom = rootChain.second();
				if (headFinal) {
					chainBottom.addPSChild(depRoot);
					chainBottom.addPSChild(headRoot);
				} else {
					chainBottom.addPSChild(headRoot);
					chainBottom.addPSChild(depRoot);
				}
				psRoots.add(chainTop);
			}
		}
		return psRoots;
	}
	
	public static List<Node> getPSSubtreesFromChain(ProjectionNode projChain) {
		List<Node> roots = new LinkedList<Node>();
		if (projChain instanceof BiProjectionNode) {
			BiProjectionNode biProjChain = (BiProjectionNode) projChain;
			if (projChain.hasChild()) {
				List<Node> headRoots = getPSSubtreesFromChain(biProjChain.getChild());
				if (biProjChain.hasLeftDependentForests()) {
					List<Node> depRoots = getPSSubtreesFromChains(biProjChain.getInnerLeftDependentForest());
					roots.addAll(getPSSubtrees(headRoots, depRoots, projChain.getRootLabels(), true));
				}
				if (biProjChain.hasRightDependentForests()) {
					List<Node> depRoots = getPSSubtreesFromChains(biProjChain.getInnerRightDependentForest());
					roots.addAll(getPSSubtrees(headRoots, depRoots, projChain.getRootLabels(), false));
				}
			}
		}
		else if (projChain instanceof UniProjectionNode){
			UniProjectionNode uniProjChain = (UniProjectionNode) projChain;
			Pair<Node, Node> rootChain = getRootChain(projChain.getRootLabels());
			Node chainTop = rootChain.first();
			Node chainBottom = rootChain.second();
			Token token = new Token();
			token.setWord(uniProjChain.getWord());
			chainBottom.addPSChild(token);
			roots.add(chainTop);
		}
		return roots;
	}
	
	public static List<Node> getPSSubtreesFromChains(List<ProjectionNode> projChains) {
		List<Node> roots = new LinkedList<Node>();
		for (ProjectionNode projChain: projChains) {
			List<Node> chainRoots = getPSSubtreesFromChain(projChain);
			roots.addAll(chainRoots);
		}
		return roots;
	}
	
	public static List<Tree> getPSTreesFromChains(List<ProjectionNode> projChains) {
		List<Tree> trees = new LinkedList<Tree>();
		List<Node> roots = getPSSubtreesFromChains(projChains);
		for(Node root: roots) {
			Tree tree = new Tree("PS");
			tree.setPSRoot(root);
			trees.add(tree);
		}
		return trees;
	}
	/***
	 * END GET TREES
	 * 
	 */
	
	public static Map<BiDSPattern, List<BiPSPattern>> readBinaryMapping(String binaryRulePath) {
		Map<BiDSPattern, List<BiPSPattern>> biRuleMap = new HashMap<BiDSPattern, List<BiPSPattern>>();
		try {
			BufferedReader br = new BufferedReader(new FileReader(new File(binaryRulePath)));
			RuleReader rr = new RuleReader();
			String line = null;
			while ((line = br.readLine()) != null) {
				BiRule rule = rr.readBinaryRule(line);
				BiDSPattern dsPatt = rule.getDSPattern();
				BiPSPattern psPatt = rule.getPSPattern();
				if (biRuleMap.containsKey(dsPatt)) {
					biRuleMap.get(dsPatt).add(psPatt);
				} else {
					List<BiPSPattern> psPatts = new LinkedList<BiPSPattern>();
					psPatts.add(psPatt);
					biRuleMap.put(dsPatt, psPatts);
				}
			}
		}
		catch(IOException e) {
			String errStr = String.format("binary rule file '%s' could not be read", binaryRulePath);
			pHandler.handle(e, errStr);
			System.exit(-1);
		}
		return biRuleMap;
	}
	
	public static Map<UniDSPattern, List<UniPSPattern>> readUnaryMapping(String unaryRulePath) {
		Map<UniDSPattern, List<UniPSPattern>> uniRuleMap = new HashMap<UniDSPattern, List<UniPSPattern>>();
		try {
			BufferedReader br = new BufferedReader(new FileReader(new File(unaryRulePath)));
			RuleReader rr = new RuleReader();
			String line = null;
			while ((line = br.readLine()) != null) {
				UniRule rule = rr.readUnaryRule(line);
				UniDSPattern dsPatt = rule.getDSPattern();
				UniPSPattern psPatt = rule.getPSPattern();
				if (uniRuleMap.containsKey(dsPatt)) {
					uniRuleMap.get(dsPatt).add(psPatt);
				} else {
					List<UniPSPattern> psPatts = new LinkedList<UniPSPattern>();
					psPatts.add(psPatt);
					uniRuleMap.put(dsPatt, psPatts);
				}
			}
		}
		catch(IOException e) {
			String errStr = String.format("unary rule file '%s' could not be read", unaryRulePath);
			pHandler.handle(e, errStr);
			System.exit(-1);
		}
		return uniRuleMap;
	}
	
	public static <X, Y> void printRuleStats(Map<X, List<Y>> mapping, String ruleType) {
		int numDSPatts = 0;
		int numAmbigDSPatts = 0;
		int numRules = 0;
		numDSPatts = mapping.size();
		for (List<Y> psPatts: mapping.values()) {
			int numPSPattsForDSPatt = psPatts.size();
			if (numPSPattsForDSPatt > 1) {
				numAmbigDSPatts++;
			}
			numRules += numPSPattsForDSPatt;
		}
		System.out.println(String.format("A total of %d %s rules were read.", numRules, ruleType));
		System.out.println(String.format("Of %d DS patterns, %d were ambiguous.\n", numDSPatts, numAmbigDSPatts));
	}
	
	public static List<Tree> applyRules(Tree tree, Map<UniDSPattern, List<UniPSPattern>> uniRuleMap, 
			Map<BiDSPattern, List<BiPSPattern>> biRuleMap, Map<String, String> stackingRules, 
			Configuration config) {
		System.out.println(String.format("Building projection chains"));
		buildProjectionChains(tree.getDSRoot(), tree, uniRuleMap, biRuleMap, config);
		int numProjChains = tree.getDSRoot().getProjForest().size();
		System.out.println(String.format("Built: root has %d projection chains.", numProjChains));
		int numProjChainsAfterFilter = numProjChains;
		if(tree.getDSRoot().getProjForest().size() > 0) {
			filterStackingOrders(tree, stackingRules);
			printStackingAmbiguities(tree);
			numProjChainsAfterFilter = tree.getDSRoot().getProjForest().size();
			System.out.println(String.format("Filtered: root has %d projection chains.", numProjChainsAfterFilter));
		}
		return getPSTreesFromChains(tree.getDSRoot().getProjForest());
	}
	public static void printPerformanceStats(ExampleDoc exf) {
		printPerformanceStats(exf, new LinkedList<String>(exf.getIDs()));
	}
	public static void printPerformanceStats(ExampleDoc exf, List<String> exampleIDs) {
		int numSents = exampleIDs.size();
		int numSentsWithTrees = 0;
		int numSentsWithTreesOneToEight = 0;
		int numSentsWithTreesEightPlus = 0;
		int numTrees = 0;
		int numTreesOneToEight = 0;
		int numTreesEightPlus = 0;
		for (String exampleID: exampleIDs) {
			Example ex = exf.getExampleWithID(exampleID);
			int sentLength = ex.getOriginal().split(" ").length;
			int numTreesForSent = ex.getSystemTreeReps().size();
			if(numTreesForSent > 0) {
				numSentsWithTrees++;
				numTrees += numTreesForSent;
				if(sentLength <= 8) {
					numSentsWithTreesOneToEight++;
					numTreesOneToEight += numTreesForSent;
				}
				else {
					numSentsWithTreesEightPlus++;
					numTreesEightPlus += numTreesForSent;
				}			
			} 
		}
		double ambigSentsWithTrees = 0.0;
		if (numSentsWithTrees > 0) {
			ambigSentsWithTrees = (double) numTrees / numSentsWithTrees;
		}
		double ambigSentsWithTreesOneToEight = 0.0;
		if (numSentsWithTreesOneToEight > 0) {
			ambigSentsWithTreesOneToEight = (double) numTreesOneToEight / numSentsWithTreesOneToEight;
		}
		double ambigSentsWithTreesEightPlus = 0.0;
		if (numSentsWithTreesEightPlus > 0) {
			ambigSentsWithTreesEightPlus = (double) numTreesEightPlus / numSentsWithTreesEightPlus;
		}	
		System.out.println("Performance statistics:");
		System.out.println(String.format("%d/%d sentences have PS output\n", numSentsWithTrees, numSents));
		System.out.println(String.format("Average ambiguity for sentences with PS output:\n%.2f PS trees per sentence", 
				ambigSentsWithTrees));
		System.out.println(String.format("%.2f PS trees per sentence for %d sentences length 1-8", ambigSentsWithTreesOneToEight, 
				numSentsWithTreesOneToEight));
		System.out.println(String.format("%.2f PS trees per sentence for %d sentences length 8+", ambigSentsWithTreesEightPlus, 
				numSentsWithTreesEightPlus));
		System.out.println();
	}
	private static void applyRulesToInputFile(Map<UniDSPattern, List<UniPSPattern>> uniRuleMap, 
			Map<BiDSPattern, List<BiPSPattern>> biRuleMap, Map<String, String> stackingRules, 
			Configuration config) {
		int numExamples = 0;
		int numCompat = 0;
		int numProcessed = 0;
		try {
			ExampleDocReader exfr = ExampleDocReader.newReader(opts.inputFilePath, "xml");
			ExampleDoc exf = exfr.read();
			ExampleDocWriter exfw = ExampleDocWriter.newWriter(opts.outputFilePath, "xml");
			TreeReader dsr = TreeReader.newReader(opts.dsTreeFormat);
			TreeWriter psw = TreeWriter.newWriter(opts.psTreeFormat);
			Example exr = null;
			while ((exr = exf.getNextExample()) != null) {
				numExamples++;
				if (ConvertUtils.isCompatible(exr)) {
					numCompat++;
					try {
						Tree dsTree = dsr.readTree(exr.getTreeRep("DS-Const"));
						System.out.println(String.format("Processing example '%s'", exr.getID()));
						List<Tree> systemTrees = applyRules(dsTree, uniRuleMap, biRuleMap, stackingRules, config);
						System.out.println();
						for (Tree systemTree: systemTrees) {
							exr.addSystemTreeRep(psw.writeTree(systemTree));
						}
						numProcessed++;
					}
					catch (UnreadableRepresentationException e) {
						String errStr = String.format("Could not process representation for example '%s'", exr.getID());
						pHandler.handle(e, errStr);
					}
				}
				exfw.writeExample(exr);
			}
			printPerformanceStats(exf);
			exfw.close();
			System.out.println("Apply rules was successful.");
			System.out.println(String.format("%d/%d compatible trees were processed", numProcessed, numCompat));
			System.out.println(String.format("Processed %d total examples\n", numExamples));
			
		} catch(Exception e) {
			pHandler.handle(e, "apply rules failed.", System.out);
			e.printStackTrace(System.out);
			System.exit(-1);
		}
		
	}
	
	public static void main(String[] args) {
		CmdLineParser parser = new CmdLineParser(opts);
		try {
			parser.parseArgument(args);
			Configuration config;
			if (opts.configFilePath != null) {
				XMLConfiguration xmlConfig = new XMLConfiguration(opts.configFilePath);
				config = Configuration.init(xmlConfig);
			} else {
				config = Configuration.initDefault();
			}
			Map<UniDSPattern, List<UniPSPattern>> uniRuleMap = readUnaryMapping(opts.uniRuleFilePath);
			printRuleStats(uniRuleMap, "unary");
			Map<BiDSPattern, List<BiPSPattern>> biRuleMap = readBinaryMapping(opts.biRuleFilePath);
			printRuleStats(biRuleMap, "binary");
			Map<String, String> stackingRules = ConvertUtils.getStackingRules();
			applyRulesToInputFile(uniRuleMap, biRuleMap, stackingRules, config);
		}

		catch(CmdLineException e) {
			System.out.println(e.getMessage());
			System.out.print("Usage: ApplyRules");
			parser.printSingleLineUsage(System.out);
			System.out.println();
			parser.printUsage(System.out);
			System.exit(-1);
		}
		catch(ConfigurationException e) {
			String errStr = String.format("configuration file \'%s\' could not be read.", opts.configFilePath);
			pHandler.handle(e, errStr);
			System.exit(-1);
		}
	}
}
